package com.wg.net.protocols;

import cn.hutool.core.util.ZipUtil;
import com.wg.core.io.BigEndianBinaryReader;
import com.wg.core.io.BigEndianBinaryWriter;
import com.wg.net.*;
import com.wg.net.operations.base.OperationRequest;
import com.wg.net.operations.base.OperationResponse;
import com.wg.net.operations.base.S2SOperationRequest;
import com.wg.net.operations.base.S2SOperationResponse;

public class BinaryByteProtocol implements IProtocol {
    private final IMessageHeaderConverter headerConverter;
    private final byte headerSize;

    public static BinaryByteProtocol getInstance() {
        return Singleton.INSTANCE.getManager();
    }

    public BinaryByteProtocol(IMessageHeaderConverter headerConverter) {
        this.headerConverter = headerConverter;
        this.headerSize = headerConverter.getHeaderSize();
    }

    @Override
    public MessageHeader parseMessageHeader(byte[] data) {
        return MessageHeaderConverterBinary.getInstance().parseHeader(data);
    }

    @Override
    public byte[] serializeInitRequest(OperationRequest request) {
        return serializeOperationRequest(request, MessageType.InitRequest);
    }

    @Override
    public byte[] serializeInitResponse(OperationResponse response) {
        return serializeOperationResponse(response, MessageType.InitResponse);
    }

    @Override
    public OperationRequest parseOperationRequest(byte[] data, boolean isZip) {
        byte[] buffer = getUnZipBytes(data, isZip);

        BigEndianBinaryReader reader = new BigEndianBinaryReader(buffer);
        OperationRequest request = new OperationRequest();
        request.read(reader);
        return request;
    }

    @Override
    public byte[] serializeOperationRequest(OperationRequest request, MessageType messageType) {
        BigEndianBinaryWriter writer = new BigEndianBinaryWriter(32);
        request.write(writer);
        return getZipBytes(messageType, writer);
    }

    @Override
    public OperationResponse parseOperationResponse(byte[] data, boolean isZip) {
        byte[] buffer = getUnZipBytes(data, isZip);

        BigEndianBinaryReader reader = new BigEndianBinaryReader(buffer);
        OperationResponse response = new OperationResponse();
        response.read(reader);
        return response;
    }

    @Override
    public byte[] serializeOperationResponse(OperationResponse response, MessageType messageType) {
        BigEndianBinaryWriter writer = new BigEndianBinaryWriter(32);
        response.write(writer);
        return getZipBytes(messageType, writer);
    }

    @Override
    public S2SOperationRequest parseS2SOperationRequest(byte[] data, boolean isZip) {
        byte[] buffer = getUnZipBytes(data, isZip);

        BigEndianBinaryReader reader = new BigEndianBinaryReader(buffer);

        S2SOperationRequest request = new S2SOperationRequest();
        request.read(reader);
        return request;
    }

    @Override
    public byte[] serializeS2SOperationRequest(S2SOperationRequest request, MessageType messageType) {
        BigEndianBinaryWriter writer = new BigEndianBinaryWriter(32);
        request.write(writer);
        return getZipBytes(messageType, writer);
    }

    @Override
    public S2SOperationResponse parseS2SOperationResponse(byte[] data, boolean isZip) {
        byte[] buffer = getUnZipBytes(data, isZip);

        BigEndianBinaryReader reader = new BigEndianBinaryReader(buffer);
        S2SOperationResponse response = new S2SOperationResponse();
        response.read(reader);
        return response;
    }

    @Override
    public byte[] serializeS2SOperationResponse(S2SOperationResponse response, MessageType messageType) {
        BigEndianBinaryWriter writer = new BigEndianBinaryWriter(32);
        response.write(writer);
        return getZipBytes(messageType, writer);
    }

    private byte[] getUnZipBytes(byte[] data, boolean isZip) {
        byte[] buffer = new byte[data.length - this.headerSize];
        System.arraycopy(data, this.headerSize, buffer, 0, buffer.length);
        if (isZip) {
            buffer = ZipUtil.unGzip(buffer);
        }
        return buffer;
    }

    private byte[] getZipBytes(MessageType messageType, BigEndianBinaryWriter writer) {
        byte[] data = writer.toByteArray();
        boolean isZip = data.length > NetConfig.getInstance().getGzipThreshold();
        if (isZip) {
            data = ZipUtil.gzip(data);
        }

        BigEndianBinaryWriter writer1 = new BigEndianBinaryWriter(data.length + headerSize);
        this.headerConverter.writeHeader(writer1, messageType, isZip);
        writer1.writeBytes(data);
        return writer1.toByteArray();
    }

    private enum Singleton {
        INSTANCE;
        BinaryByteProtocol manager;

        Singleton() {
            this.manager = new BinaryByteProtocol(MessageHeaderConverterBinary.getInstance());
        }

        public BinaryByteProtocol getManager() {
            return manager;
        }
    }
}
